﻿using Language.Utility;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods;
using System;
using static Language.Intellisense.SmartIndent.Utility;

namespace Language.Intellisense.SmartIndent
{
    class Indenter : ISmartIndent
    {
        #region Private Data

        private ITextView _textView;
        private Settings _settings;

        #endregion Private Data

        #region C-Tor

        public Indenter( ITextView textView, Settings settings )
        {
            _textView = textView;
            _settings = settings;
        }

        #endregion C-Tor
        
        #region ISmartIndent

        int? ISmartIndent.GetDesiredIndentation( ITextSnapshotLine line )
        {
            if ( _settings.IsSmartIndentationEnabled )
                return CalculateSmartIndentation( line );
            else if ( _settings.IsBlockIndentationEnabled )
                return CalculateBlockIndentation( line );

            return null;
        }

        #endregion ISmartIndent

        #region Private Methods

        private int CalculateBlockIndentation( ITextSnapshotLine line )
        {
            ITextSnapshotLine previous = GetPreviousNonEmptyLine( line );

            if ( previous == null )
                return 0;

            IndentationAnalysis indentation = _textView.AnalyseIndentation( previous );

            return indentation.Length;
        }

        private int CalculateSmartIndentation( ITextSnapshotLine line )
        {
            ITextSnapshotLine previous = GetPreviousNonEmptyLine( line );

            if ( previous == null )
                return 0;

            IndentationAnalysis indentation = _textView.AnalyseIndentation( previous );

            int imbalance = GetBraceImbalance( indentation.Text );

            return Math.Max( 0, indentation.Length + ( _textView.Options.GetTabSize() * imbalance ) );
        }

        private ITextSnapshotLine GetPreviousLine( ITextSnapshotLine line )
        {
            if ( line.LineNumber == 0 )
                return null;

            return line.Snapshot.GetLineFromLineNumber( line.LineNumber - 1 );
        }

        private ITextSnapshotLine GetPreviousNonEmptyLine( ITextSnapshotLine line )
        {
            ITextSnapshotLine previous = GetPreviousLine( line );

            if ( previous == null )
                return null;

            string text = previous.GetText();
            if ( string.IsNullOrWhiteSpace( text ) )
                return GetPreviousNonEmptyLine( previous );

            return previous;
        }

        private static int Count( string haystack, char needle )
        {
            int count = 0;

            foreach ( char c in haystack )
                if ( c == needle )
                    ++count;

            return count;
        }

        private int GetBraceImbalance( string text )
        {
            int openBraceCount = Count( text, '{' );
            int closeBraceCount = Count( text, '}' );

            return openBraceCount - closeBraceCount;
        }

        #endregion Private Methods

        #region IDisposable Support
        private bool disposedValue = false; // To detect redundant calls

        protected virtual void Dispose( bool disposing )
        {
            if ( !disposedValue )
            {
                if ( disposing )
                {
                    // TODO: dispose managed state (managed objects).
                }

                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
                // TODO: set large fields to null.

                disposedValue = true;
            }
        }

        // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
        // ~Indenter() {
        //   // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        //   Dispose(false);
        // }

        // This code added to correctly implement the disposable pattern.
        void IDisposable.Dispose()
        {
            // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
            Dispose( true );
            // TODO: uncomment the following line if the finalizer is overridden above.
            // GC.SuppressFinalize(this);
        }
        #endregion
    }
}
